/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// ----------------------------------------------------------------------------
//  This class is largely adapted from "com.google.common.base.Preconditions",
//  which is part of the "Guava" library.
//
//  Because of frequent issues with dependency conflicts, this class was
//  added to the Flink code base to reduce dependency on Guava.
// ----------------------------------------------------------------------------

package org.apache.flink.util;

import org.apache.flink.annotation.Internal;

import javax.annotation.Nullable;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * A collection of static utility methods to validate input.
 *
 * <p>This class is modelled after Google Guava's Preconditions class, and partly takes code from
 * that class. We add this code to the Flink code base in order to reduce external dependencies.
 */
@Internal
public final class Preconditions {

    // ------------------------------------------------------------------------
    //  Null checks
    // ------------------------------------------------------------------------

    /**
     * Ensures that the given object reference is not null. Upon violation, a {@code
     * NullPointerException} with no message is thrown.
     *
     * @param reference The object reference
     * @return The object reference itself (generically typed).
     * @throws NullPointerException Thrown, if the passed reference was null.
     */
    public static <T> T checkNotNull(@Nullable T reference) {
        if (reference == null) {
            throw new NullPointerException();
        }
        return reference;
    }

    /**
     * Ensures that the given object reference is not null. Upon violation, a {@code
     * NullPointerException} with the given message is thrown.
     *
     * @param reference The object reference
     * @param errorMessage The message for the {@code NullPointerException} that is thrown if the
     *     check fails.
     * @return The object reference itself (generically typed).
     * @throws NullPointerException Thrown, if the passed reference was null.
     */
    public static <T> T checkNotNull(@Nullable T reference, @Nullable String errorMessage) {
        if (reference == null) {
            throw new NullPointerException(String.valueOf(errorMessage));
        }
        return reference;
    }

    /**
     * Ensures that the given object reference is not null. Upon violation, a {@code
     * NullPointerException} with the given message is thrown.
     *
     * <p>The error message is constructed from a template and an arguments array, after a similar
     * fashion as {@link String#format(String, Object...)}, but supporting only {@code %s} as a
     * placeholder.
     *
     * @param reference The object reference
     * @param errorMessageTemplate The message template for the {@code NullPointerException} that is
     *     thrown if the check fails. The template substitutes its {@code %s} placeholders with the
     *     error message arguments.
     * @param errorMessageArgs The arguments for the error message, to be inserted into the message
     *     template for the {@code %s} placeholders.
     * @return The object reference itself (generically typed).
     * @throws NullPointerException Thrown, if the passed reference was null.
     */
    public static <T> T checkNotNull(
            T reference,
            @Nullable String errorMessageTemplate,
            @Nullable Object... errorMessageArgs) {

        if (reference == null) {
            throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
        }
        return reference;
    }

    // ------------------------------------------------------------------------
    //  Boolean Condition Checking (Argument)
    // ------------------------------------------------------------------------

    /**
     * Checks the given boolean condition, and throws an {@code IllegalArgumentException} if the
     * condition is not met (evaluates to {@code false}).
     *
     * @param condition The condition to check
     * @throws IllegalArgumentException Thrown, if the condition is violated.
     */
    public static void checkArgument(boolean condition) {
        if (!condition) {
            throw new IllegalArgumentException();
        }
    }

    /**
     * Checks the given boolean condition, and throws an {@code IllegalArgumentException} if the
     * condition is not met (evaluates to {@code false}). The exception will have the given error
     * message.
     *
     * @param condition The condition to check
     * @param errorMessage The message for the {@code IllegalArgumentException} that is thrown if
     *     the check fails.
     * @throws IllegalArgumentException Thrown, if the condition is violated.
     */
    public static void checkArgument(boolean condition, @Nullable Object errorMessage) {
        if (!condition) {
            throw new IllegalArgumentException(String.valueOf(errorMessage));
        }
    }

    /**
     * Checks the given boolean condition, and throws an {@code IllegalArgumentException} if the
     * condition is not met (evaluates to {@code false}).
     *
     * @param condition The condition to check
     * @param errorMessageTemplate The message template for the {@code IllegalArgumentException}
     *     that is thrown if the check fails. The template substitutes its {@code %s} placeholders
     *     with the error message arguments.
     * @param errorMessageArgs The arguments for the error message, to be inserted into the message
     *     template for the {@code %s} placeholders.
     * @throws IllegalArgumentException Thrown, if the condition is violated.
     */
    public static void checkArgument(
            boolean condition,
            @Nullable String errorMessageTemplate,
            @Nullable Object... errorMessageArgs) {

        if (!condition) {
            throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
        }
    }

    // ------------------------------------------------------------------------
    //  Boolean Condition Checking (State)
    // ------------------------------------------------------------------------

    /**
     * Checks the given boolean condition, and throws an {@code IllegalStateException} if the
     * condition is not met (evaluates to {@code false}).
     *
     * @param condition The condition to check
     * @throws IllegalStateException Thrown, if the condition is violated.
     */
    public static void checkState(boolean condition) {
        if (!condition) {
            throw new IllegalStateException();
        }
    }

    /**
     * Checks the given boolean condition, and throws an {@code IllegalStateException} if the
     * condition is not met (evaluates to {@code false}). The exception will have the given error
     * message.
     *
     * @param condition The condition to check
     * @param errorMessage The message for the {@code IllegalStateException} that is thrown if the
     *     check fails.
     * @throws IllegalStateException Thrown, if the condition is violated.
     */
    public static void checkState(boolean condition, @Nullable Object errorMessage) {
        if (!condition) {
            throw new IllegalStateException(String.valueOf(errorMessage));
        }
    }

    /**
     * Checks the given boolean condition, and throws an {@code IllegalStateException} if the
     * condition is not met (evaluates to {@code false}).
     *
     * @param condition The condition to check
     * @param errorMessageTemplate The message template for the {@code IllegalStateException} that
     *     is thrown if the check fails. The template substitutes its {@code %s} placeholders with
     *     the error message arguments.
     * @param errorMessageArgs The arguments for the error message, to be inserted into the message
     *     template for the {@code %s} placeholders.
     * @throws IllegalStateException Thrown, if the condition is violated.
     */
    public static void checkState(
            boolean condition,
            @Nullable String errorMessageTemplate,
            @Nullable Object... errorMessageArgs) {

        if (!condition) {
            throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
        }
    }

    /**
     * Ensures that the given index is valid for an array, list or string of the given size.
     *
     * @param index index to check
     * @param size size of the array, list or string
     * @throws IllegalArgumentException Thrown, if size is negative.
     * @throws IndexOutOfBoundsException Thrown, if the index negative or greater than or equal to
     *     size
     */
    public static void checkElementIndex(int index, int size) {
        checkArgument(size >= 0, "Size was negative.");
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
    }

    /**
     * Ensures that the given index is valid for an array, list or string of the given size.
     *
     * @param index index to check
     * @param size size of the array, list or string
     * @param errorMessage The message for the {@code IndexOutOfBoundsException} that is thrown if
     *     the check fails.
     * @throws IllegalArgumentException Thrown, if size is negative.
     * @throws IndexOutOfBoundsException Thrown, if the index negative or greater than or equal to
     *     size
     */
    public static void checkElementIndex(int index, int size, @Nullable String errorMessage) {
        checkArgument(size >= 0, "Size was negative.");
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException(
                    String.valueOf(errorMessage) + " Index: " + index + ", Size: " + size);
        }
    }

    /**
     * Ensures that future has completed normally.
     *
     * @throws IllegalStateException Thrown, if future has not completed or it has completed
     *     exceptionally.
     */
    public static void checkCompletedNormally(CompletableFuture<?> future) {
        checkState(future.isDone());
        if (future.isCompletedExceptionally()) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    // ------------------------------------------------------------------------
    //  Utilities
    // ------------------------------------------------------------------------

    /**
     * A simplified formatting method. Similar to {@link String#format(String, Object...)}, but with
     * lower overhead (only String parameters, no locale, no format validation).
     *
     * <p>This method is taken quasi verbatim from the Guava Preconditions class.
     */
    private static String format(@Nullable String template, @Nullable Object... args) {
        final int numArgs = args == null ? 0 : args.length;
        template = String.valueOf(template); // null -> "null"

        // start substituting the arguments into the '%s' placeholders
        StringBuilder builder = new StringBuilder(template.length() + 16 * numArgs);
        int templateStart = 0;
        int i = 0;
        while (i < numArgs) {
            int placeholderStart = template.indexOf("%s", templateStart);
            if (placeholderStart == -1) {
                break;
            }
            builder.append(template.substring(templateStart, placeholderStart));
            builder.append(args[i++]);
            templateStart = placeholderStart + 2;
        }
        builder.append(template.substring(templateStart));

        // if we run out of placeholders, append the extra args in square braces
        if (i < numArgs) {
            builder.append(" [");
            builder.append(args[i++]);
            while (i < numArgs) {
                builder.append(", ");
                builder.append(args[i++]);
            }
            builder.append(']');
        }

        return builder.toString();
    }

    // ------------------------------------------------------------------------

    /** Private constructor to prevent instantiation. */
    private Preconditions() {}
}
